/**
 * WTF Engine
 *
 * Released under the terms of the Worcester Public License,
 * see LICENSE in the root folder. This program comes with
 * no warranty whatsoever, implied or otherwise. All rights
 * other than those explicitly specified in the LICENSE document
 * are reserved.
 **
 * Data & Cache Manager
 **
 * Author: Sean Micklethwaite
 * Magicness rating: 9/10
 **/

#ifndef __dataH__
#define __dataH__

#include "kernel.h"
#include "loggable.h"
#include "string.h"
#include "serializable.h"
#include <map>

using namespace Rtl;

namespace WtfEngine
{
	template <class T> class CacheRef;
	template <class T> class PolymorphicCacheRef;

#define DATA_INTERFACE_DEFS(T, RT) \
	typedef WtfEngine::RT<T> tCacheRef;\
	typedef const tCacheRef& tCacheRefa;

	/**
	 * Data objects should place this macro at the end of their definition,
	 * in order to report the correct size to the garbage collector.
	 *
	 * \param T		The class of the object
	 * \param Size	The total size in memory of the object and its members
	 **/
#define DATA_OBJECT(T, Size)	GC_OBJECT(T, Size);\
	DATA_INTERFACE_DEFS(T, PolymorphicCacheRef)

#define DATA_INTERFACE(T)	GC_INTERFACE(T); \
	DATA_INTERFACE_DEFS(T, PolymorphicCacheRef)

#define __DATA_LOADABLE		__Data_Class_Is_Not_Loadable

	/**
	 * Loadable data objects should use this macro, to tell
	 * the data manager that the type is loadable (i.e. the class
	 * provides a static Load function).
	 * \param T	The class of the object
	 **/
#define DATA_LOADABLE(T, Size)	GC_OBJECT(T, Size)	\
	public: typedef T __DATA_LOADABLE;				\
	DATA_INTERFACE_DEFS(T, CacheRef);				\
	virtual tLoadFunc getLoadFunc() const { return &T::Load; }


	/**
	 * Reconstructible data types can register a reconstructor function that
	 * will rebuild the data when needed. This is useful for objects which require
	 * custom runtime parameters, or for objects which have no physical backing
	 * and are always constructed programatically.
	 **/
#define DATA_RECONSTRUCTIBLE(T, Size)	GC_OBJECT(T, Size)	\
	public: typedef T __DATA_LOADABLE;				\
	DATA_INTERFACE_DEFS(T, PolymorphicCacheRef); \
	static WtfEngine::IDataObject * Load(const WtfEngine::StringPool::Entry& sName) { \
		return WtfEngine::DataManager::GetInstance().Reconstruct(sName); \
	}


	/**** Exceptions ****/

	class ExClassNotLoadable: public std::logic_error {
	public:
		ExClassNotLoadable(const String& sName)
		: std::logic_error(String("Invalid operation: class not loadable - data: ") + sName) {
		};
	};
	class ExObjectNotReconstructible: public std::logic_error {
	public:
		ExObjectNotReconstructible(const String& sName)
		: std::logic_error(String("Invalid operation: object not reconstructible - data: ") + sName) {
		};
	};


	/**
	 * Generic data object interface
	 **/
	class IDataObject: public virtual IGcObject {
	public:
		typedef IDataObject * (* tLoadFunc )(const StringPool::Entry& sName);

		virtual StringPool::Entry getName() const = 0;

		inline String getNameString() const{
			return getName();
		};

		virtual tLoadFunc getLoadFunc() const { return &Load; };

		static IDataObject * Load(const StringPool::Entry& sFilePath){
			throw ExClassNotLoadable(sFilePath);
		};

		DATA_INTERFACE(IDataObject);
	};


	
	/**
	 * The base class for all data objects.
	 *
	 * Data objects which are managed by the data manager must overload
	 * 	static T * Load(const StringPool::Entry& sFilePath)
	 * in order to allow objects to be loaded on demand. These objects
	 * are expected to be immutable, as they will be shared.
	 **/
	class DataObject : public virtual IDataObject, public GcObject, public Loggable<DataObject>
	{
	private:
		/// Name (URI) of the object - stored as an entry in the string pool to save memory. The
		/// actual string will be shared between this object and all its cache references.
		StringPool::Entry msName;

	public:
		DataObject(const StringPool::Entry& name = StringPool::GetInstance()["undefined"]);

		virtual ~DataObject() {
			this->Log(LOG_INFO, String("Data: ") + *msName + " destroyed");
		};

		StringPool::Entry getName() const { return msName; };

		DATA_OBJECT(DataObject, sizeof(DataObject));
	};


	/**
	 * This represents a mapping from logical paths to every
	 * data object available to the game. Here, by assuming that
	 * data objects are immutable, we can save memory by only loading
	 * one instance of a particular data object. An instance of a
	 * data object can be retrieved by calling Load, passing the logical
	 * path to the object.
	 *
	 * ABSTRACT:
	 * 	data : seq char <-> (T)
	 **/
	class DataManager : public Singleton<DataManager>, public Loggable<DataManager>
	{
	public:
		typedef FunctorType<IDataObject*, const StringPool::Entry&>::tRef tReconstructor;
		typedef std::map<StringPool::Entry, IDataObject::tCacheRef> tObjectMap;
		typedef std::map<StringPool::Entry, tReconstructor> tReconstructorMap;
		typedef int tDataTypeId;

	private:
		tObjectMap mObjects;
		tReconstructorMap mReconstructors;

		std::vector<IDataObject::tLoadFunc> mvRegisteredTypes;

		IDataObject *           Lookup(const StringPool::Entry& sNameEntry);
		IDataObject::tCacheRef*	Lookup(const StringPool::Entry& sNameEntry, IDataObject::tLoadFunc f);

	public:
		DataManager() : Singleton<DataManager>(), Loggable<DataManager>("Data Manager"), mObjects() {};

		/**
		 * Load(s : seq char) = r : (T)
		 * @post r = data(s)
		 * @note	This function is only valid for non-polymorphic data classes.
		 *			It expects a static Load method, which will return an instance
		 *			of the class.
		 **/
		template <typename T>
		T* Load(const StringPool::Entry& sName) {
			return Load<T>(sName, (IDataObject::tLoadFunc) &T::Load);
		};

		template <typename T>
		T* Load(const StringPool::Entry& sName, IDataObject::tLoadFunc pLoadFunc){
			T *						pObj;
			IDataObject::tCacheRef*	prObj = Lookup(sName, pLoadFunc);

			//
			// Load the object in if it is not present.
			//

			if(prObj->typename BasicWeakRef<IDataObject,IDataObject::tCacheRef>::getRef() == NULL) {
				*prObj = (*pLoadFunc)(sName);
			};
			

			//
			// Perform type check, and return pointer
			//

			if((pObj = dynamic_cast<T*>(&**prObj))) {
				return pObj;
			} else {
				throw std::logic_error(String("Loading data object returned invalid type: ") + sName);
			};
		};

		template <typename T>
		T * Get(const StringPool::Entry& sName) {
			return dynamic_cast<T*>(Lookup(sName));
		};


		/**
		 * Set(r : (T))
		 * @post data(r.name) = r
		 **/
		template <typename T>
		void Set(T * pObj){
			*Lookup(pObj->getName(), &T::Load) = pObj;
		};

		template <typename T>
		void Set(T * pObj, IDataObject::tLoadFunc f){
			*Lookup(pObj->getName(), f) = pObj;
		};

		void Insert(const PolymorphicCacheRef<IDataObject>& rObj);


		/**
		 * Frees all objects which are not being used elsewhere
		 **/
		void Flush(){

		};

		void RegisterReconstructor(const StringPool::Entry& sName, const tReconstructor& rFunc) {
			mReconstructors[sName] = rFunc;
		};
		void DeregisterReconstructor(const StringPool::Entry& sName) {
			mReconstructors.erase(sName);
		};
		static IDataObject * Reconstruct(const StringPool::Entry& sName);

		tDataTypeId            RegisterType(const String& sTypeName, IDataObject::tLoadFunc pLoadFunc);
		const String&          LookupTypeName(tDataTypeId id);
		IDataObject::tLoadFunc LookupLoadFunc(tDataTypeId id);
		tDataTypeId            LookupRegisteredType(IDataObject::tLoadFunc pLoadFunc);

		const tObjectMap& getObjectMap() const {
			return mObjects;
		};
		const tReconstructorMap& getReconstructorMap() const {
			return mReconstructors;
		};
	};


	/**** Reference Types ****/

	template <class tThis> struct GetDataObjectName {};
	template <class T> struct GetDataObjectName<T*> {
		inline static const StringPool::Entry& get(T* pObj) {
			return pObj ? pObj->getName() : StringPool::Entry();
		};
	};
	template <class T> struct GetDataObjectName<GcReference<T> > {
		inline static const StringPool::Entry& get(const GcReference<T>& r) {
			return typename GetDataObjectName<T*>::get(r.getRef());
		};
	};
	template <class T> struct GetDataObjectName<WeakRef<T> > {
		inline static const StringPool::Entry& get(const WeakRef<T>& r) {
			return typename GetDataObjectName<T*>::get(r.getRef());
		};
	};
	template <class T> struct GetDataObjectName<CacheRef<T> > {
		inline static const StringPool::Entry& get(const CacheRef<T>& r) {
			return r.getCacheName();
		};
	};
	template <class T> struct GetDataObjectName<PolymorphicCacheRef<T> > {
		inline static const StringPool::Entry& get(const PolymorphicCacheRef<T>& r) {
			return r.getCacheName();
		};
	};

	template <class tThis, class U>
	struct GcReferenceComparatorMetric<tThis, CacheRef<U> > : public GetDataObjectName<tThis> {
	};
	template <class tThis, class U>
	struct GcReferenceComparatorMetric<tThis, PolymorphicCacheRef<U> > : public GetDataObjectName<tThis> {
	};
	template <class U, class tThat>
	struct GcReferenceComparatorMetric<CacheRef<U>, tThat> : public GetDataObjectName<CacheRef<U> > {
	};
	template <class U, class tThat>
	struct GcReferenceComparatorMetric<PolymorphicCacheRef<U>, tThat> : public GetDataObjectName<PolymorphicCacheRef<U> > {
	};
	template <class T, class U>
	struct GcReferenceComparatorMetric<CacheRef<T>, CacheRef<U> > : public GetDataObjectName<CacheRef<U> > {
	};
	template <class T, class U>
	struct GcReferenceComparatorMetric<PolymorphicCacheRef<T>, PolymorphicCacheRef<U> > : public GetDataObjectName<PolymorphicCacheRef<U> > {
	};


	template <class T, class tRef>
	class BasicCacheRef: public BasicWeakRef<T, tRef> {
		StringPool::Entry	msCachedFile;

	public:
		typedef BasicWeakRef<T, tRef> tBasicRef;

		/**
		 * Constructors.
		 **/
		BasicCacheRef(): tBasicRef(), msCachedFile() {};
		BasicCacheRef(tConstString sCachedFile) : tBasicRef(), msCachedFile(sCachedFile) {
		};
		BasicCacheRef(const StringPool::Entry& sCachedFile) : tBasicRef(), msCachedFile(sCachedFile) {
		};
		template <class U>
		BasicCacheRef(U* pObj) : tBasicRef(pObj), msCachedFile(pObj->getName()) {
		};
		template <class U, class _tRef>
		BasicCacheRef(const BasicRef<U, _tRef>& rObj) : tBasicRef(rObj), msCachedFile(rObj->getName()) {
		};
		template <class U, class _tRef>
		BasicCacheRef(const BasicCacheRef<U, _tRef>& rObj) : tBasicRef(), msCachedFile(rObj.getCacheName()) {
			Init(rObj.typename BasicCacheRef<U, _tRef>::tBasicRef::getRef());
		};
		
		/**
		 * Copy constructor
		 **/
		BasicCacheRef(const BasicCacheRef<T, tRef>& rObj) : tBasicRef(), msCachedFile(rObj.getCacheName()) {
			Init(rObj.tBasicRef::getRef());
		};

		template <class U, class _tRef>
		inline void operator= (const BasicCacheRef<U, _tRef>& r_ref){
			Init(r_ref.getRef());
			msCachedFile = r_ref.getCacheName();
		};

		/**
		 * Called before dereferencing, if the reference is null, we will
		 * attempt to load in the data object.
		 **/
		T * getRef() const {
			if(this->tBasicRef::getRef() == NULL){
				assert(!msCachedFile.isNull());
				this->Init(DataManager::GetInstance().Load<T>(msCachedFile, Downcast()->getLoadFunc()));
			};
			return this->tBasicRef::getRef();
		};

		inline void Destroy() {
			if(this->tBasicRef::getRef() != NULL) {
				this->Kill();
			};
		};

		inline bool isNull() const {
			return msCachedFile.isNull();
		};
		inline const StringPool::Entry& getCacheName() const {
			return msCachedFile;
		};

		// XXX - can't make this explicit
		operator const String& () const {
			return *msCachedFile;
		};

		unsigned CalculateSerializedSize() {
			return (*msCachedFile).CalculateSerializedSize() + sizeof(int);
		};
		unsigned Serialize(void * pBuf) {

			//
			// Format is:
			//	Data string (null terminated, variable size)
			//	Data type id (int)
			//

			unsigned n = (*msCachedFile).Serialize(pBuf);
			//DataManager::GetInstance().Log(DataManager::LOG_DEBUG, String("Serializing - ") + *msCachedFile);
			*reinterpret_cast<int *>((unsigned)pBuf + n) = DataManager::GetInstance().LookupRegisteredType(Downcast()->getLoadFunc());
			return (n + sizeof(int));
		};
		unsigned Deserialize(void * pBuf) {
			String sName;
			unsigned n = sName.Deserialize(pBuf);

			*Downcast() = PolymorphicCacheRef<T>(sName, DataManager::GetInstance().LookupLoadFunc(
				*reinterpret_cast<int *>((unsigned)pBuf + n)));

			return n + sizeof(int);
		};
	};

	/**
	 * These special references point to demand loaded data objects, which can be
	 * freed from memory when not needed, prefetched, etc...
	 *
	 * By using these references in place of normal ones, you allow the Data Manager
	 * to reclaim vital memory when it needs it.
	 **/
	template <class T>
	class CacheRef : public BasicCacheRef<T, CacheRef<T> >
	{
		typedef BasicCacheRef<T, CacheRef<T> > tBasicRef;

	public:
		inline CacheRef(): tBasicRef() {};

		/**
		 * Constructor.
		 **/
		template <class U>
		CacheRef(const U& r) : tBasicRef(r) {
			//static_cast<typename T::__DATA_LOADABLE *>(this->mpObject);
		};


		/**
		 * Copy constructor - it's important that this is overridden, as
		 * the default would cause serious memory issues!
		 **/
		CacheRef(const CacheRef<T>& ref) : tBasicRef(ref) {
		};

		/**
		 * Type assignment, very important.
		 **/
		inline void operator= (const CacheRef<T>& r_ref){
			this->tBasicRef::operator=(r_ref);
		};

		const IDataObject::tLoadFunc getLoadFunc() const {
			return &T::Load;
		};
	};


	/**
	 * This version of cache references take a load function as a
	 * parameter, used when loading instances of a polymorphic
	 * class.
	 * @param	T	Base class of polymorphic type
	 **/
	template <class T>
	class PolymorphicCacheRef : public BasicCacheRef<T, PolymorphicCacheRef<T> >
	{
		typedef BasicCacheRef<T, PolymorphicCacheRef<T> > tBasicRef;

		IDataObject::tLoadFunc	mpLoadFunc;

	public:
		/// Constructor to satisfy std::map's requirement for a default.
		/// @warning	Gives an invalid reference, which shouldn't be used.
		inline PolymorphicCacheRef()
		: tBasicRef(), mpLoadFunc(NULL) {
		};

		inline explicit PolymorphicCacheRef(IDataObject::tLoadFunc f)
		: tBasicRef(), mpLoadFunc(f) {
		};
		inline PolymorphicCacheRef(tConstString sName) : tBasicRef(sName), mpLoadFunc(&typename T::Load) {
		};
		inline PolymorphicCacheRef(const StringPool::Entry& sName) : tBasicRef(sName), mpLoadFunc(&typename T::Load) {
		};
		inline PolymorphicCacheRef(const StringPool::Entry& sName, IDataObject::tLoadFunc f)
		: tBasicRef(sName), mpLoadFunc(f) {
		};

		/**
		 * Copy constructor - it's important that this is overridden, as
		 * the default would cause serious memory issues!
		 **/
		inline PolymorphicCacheRef(const PolymorphicCacheRef<T>& ref) : tBasicRef(ref), mpLoadFunc(ref.mpLoadFunc) {
		};

		/**
		 * Implicit casts from other reference/pointer types.
		 **/
		template <class U>
		PolymorphicCacheRef(U* pObj) : tBasicRef(pObj), mpLoadFunc(pObj->getLoadFunc()) {
		};
		template <class U, class tRef>
		PolymorphicCacheRef(const BasicRef<U, tRef>& rObj) : tBasicRef(rObj), mpLoadFunc(rObj->getLoadFunc()) {
		};
		template <class U, class tRef>
		PolymorphicCacheRef(const BasicCacheRef<U, tRef>& rObj) : tBasicRef(rObj), mpLoadFunc(rObj.Downcast()->getLoadFunc()) {
		};

		/**
		 * Type assignment, very important.
		 **/
		void operator= (const PolymorphicCacheRef<T>& r_ref){
			this->tBasicRef::operator=(r_ref);
			mpLoadFunc = r_ref.mpLoadFunc;
		};

		const IDataObject::tLoadFunc getLoadFunc() const {
			return mpLoadFunc;
		};

	protected:
		/// Protected pointer assignment - for use by data manager
		template <class U>
		void operator= (U* pRef) {
			Init(pRef);
		};

		friend class DataManager;
	};

	void RegisterDataTypes();
};


#endif
